package com.bazaaroid.server.gae.core.dao.partner.impl;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.ws.rs.core.MultivaluedMap;

import au.com.bytecode.opencsv.CSVReader;

import com.bazaaroid.server.gae.core.dao.impl.JDOGenericDAOImpl;
import com.bazaaroid.server.gae.core.dao.partner.CategoryDAO;
import com.bazaaroid.server.gae.core.dao.partner.DiscountDAO;
import com.bazaaroid.server.gae.core.dao.partner.LocationDAO;
import com.bazaaroid.server.gae.core.dao.partner.PartnerDAO;
import com.bazaaroid.server.gae.core.dao.partner.ProductDAO;
import com.bazaaroid.server.gae.core.dao.partner.TagDAO;
import com.bazaaroid.server.gae.core.enums.Languages;
import com.bazaaroid.server.gae.core.ioc.GuiceFactory;
import com.bazaaroid.server.gae.core.persistence.model.partner.Category;
import com.bazaaroid.server.gae.core.persistence.model.partner.Discount;
import com.bazaaroid.server.gae.core.persistence.model.partner.Location;
import com.bazaaroid.server.gae.core.persistence.model.partner.Partner;
import com.bazaaroid.server.gae.core.persistence.model.partner.Product;
import com.bazaaroid.server.gae.core.persistence.model.partner.Tag;
import com.bazaaroid.server.gae.util.NumberUtil;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

public class JDOProductDAOImpl extends JDOGenericDAOImpl<Product, Key>
		implements ProductDAO {

	@SuppressWarnings("unchecked")
	public List<Product> getProductsByPartner(Partner partner) {
		PersistenceManager persistenceManager = getPersistenceManager();
		persistenceManager.setDetachAllOnCommit(true);

		Query query = persistenceManager.newQuery(getEntityClass());
		query.setFilter("partner.contains(keyParam)");
		query.declareParameters("com.google.appengine.api.datastore.Key keyParam");
		query.setOrdering("name asc");

		List<Product> products = new ArrayList<Product>();

		try {
			products = (List<Product>) query.execute(partner.getKey());
			products.size();
		}
		finally {
			query.closeAll();
			persistenceManager.close();
		}

		return products;
	}

	@Override
	public Product getProductByPartnerAndLocalId(Partner partner,
			String localId) {
		PersistenceManager persistenceManager = getPersistenceManager();
		persistenceManager.setDetachAllOnCommit(true);

		Query query = persistenceManager.newQuery(getEntityClass());
		query.setFilter("partner == keyParam && localId == localIdParam");
		query.declareImports("import com.google.appengine.api.datastore.Key;");
		query.declareParameters("Key keyParam, String localIdParam");
		query.setUnique(true);

		Product product = null;

		try {
			product = (Product) query.execute(partner.getKey(), localId);
		}
		finally {
			query.closeAll();
			persistenceManager.close();
		}

		return product;
	}

	@SuppressWarnings("unchecked")
	public List<Key> getProductsByName(String nameParam) {
		PersistenceManager persistenceManager = getPersistenceManager();
		persistenceManager.setDetachAllOnCommit(true);

		Query query = persistenceManager.newQuery(getEntityClass());
		query.setResult("key");
		query.setFilter("name.startsWith(nameParam)");
		query.declareParameters("String nameParam");

		List<Key> products = new ArrayList<Key>();

		try {
			products = (List<Key>) query.execute(nameParam);
			products.size();
		}
		finally {
			query.closeAll();
			persistenceManager.close();
		}

		return products;
	}

	@SuppressWarnings("unchecked")
	public List<Key> getProductsByDescription(String descriptionParam) {
		PersistenceManager persistenceManager = getPersistenceManager();
		persistenceManager.setDetachAllOnCommit(true);

		Query query = persistenceManager.newQuery(getEntityClass());
		query.setResult("key");
		query.setFilter("description.startsWith(descriptionParam)");
		query.declareParameters("String descriptionParam");

		List<Key> products = new ArrayList<Key>();

		try {
			products = (List<Key>) query.execute(descriptionParam);
			products.size();
		}
		finally {
			query.closeAll();
			persistenceManager.close();
		}

		return products;
	}

	@SuppressWarnings("unchecked")
	public List<Key> getProductsByTag(String tagName) {
		PersistenceManager persistenceManager = getPersistenceManager();
		persistenceManager.setDetachAllOnCommit(true);

		Query query = persistenceManager.newQuery(getEntityClass());
		query.setResult("key");
		query.declareVariables("Tag tag");
		query.setFilter("this.tags.contains(tag) && tag.name == tagNameParam");
		query.declareImports("import com.bazaaroid.server.gae.core.persistence.model.partner.tag;");
		query.declareParameters("String tagNameParam");

		List<Key> products = new ArrayList<Key>();

		try {
			products = (List<Key>) query.execute(tagName);
			products.size();
		}
		finally {
			query.closeAll();
			persistenceManager.close();
		}

		return products;
	}

	public static Comparator<Product> priceComparator = new Comparator<Product>() {
		DiscountDAO discountDAO = GuiceFactory.getInjector()
			.getInstance(DiscountDAO.class);

		public int compare(Product product1, Product product2) {
			Discount discount1 = product1.getDiscount() != null
				? discountDAO.getById(product1.getDiscount())
				: null;
			Discount discount2 = product2.getDiscount() != null
				? discountDAO.getById(product2.getDiscount())
				: null;
			Double price1 = discount1 != null
				? discount1.getPriceWithDiscount()
				: product1.getPrice();
			Double price2 = discount2 != null
				? discount2.getPriceWithDiscount()
				: product2.getPrice();

			int cmp = price1.compareTo(price2);
			if(cmp == 0) {
				if (discount1 == discount2)
					cmp = 0;
				else if (discount1 == null || discount2 == null)
					cmp = discount1 == null ? 1 : -1;
				else
					cmp = discount2.getPercentage() - discount1.getPercentage();
			}

			return cmp;
		}
	};

	public List<Product> getProductsBySearchParams(String[] searchParams) {
		HashSet<Key> productKeys = new HashSet<Key>();
		String searchParamsStr = "";
		for(String searchParam: searchParams) {
			productKeys.addAll(getProductsByName(searchParam));
			productKeys.addAll(getProductsByDescription(searchParam));
			productKeys.addAll(getProductsByTag(searchParam));
			searchParamsStr += searchParam;
		}
		productKeys.addAll(getProductsByTag(searchParamsStr));

		List<Product> products = productKeys.isEmpty()
			? new ArrayList<Product>()
			: getByKeys(productKeys);

		Collections.sort(products, priceComparator);
		return products;
	}

	@SuppressWarnings("unchecked")
	public List<Product> getProductsByQueryParams(
			MultivaluedMap<String, String> queryParams) {

		String filter = "";
		String params = "";
		String variables = "";
		String imports = "import com.google.appengine.api.datastore.Key;";
		List<Object> values = new ArrayList<Object>();
		boolean noParams = true;

		if(queryParams.containsKey("name")) {

			if(!noParams) {
				filter += "&& ";
				params += ", ";
			}

			filter += "name == nameParam ";
			params += "String nameParam";
			values.add(queryParams.get("name").get(0));
			noParams = false;
		}

		if(queryParams.containsKey("partner")) {
			String partnerParam = queryParams.get("partner").get(0);
			Long partnerId = NumberUtil.stringToLong(partnerParam);

			if(partnerId != null) {
				if(!noParams) {
					filter += "&& ";
					params += ", ";
				}

				filter += "partner == partnerParam ";
				params += "Key partnerParam";
				values.add(KeyFactory.createKey(
						Partner.class.getSimpleName(), partnerId));
				noParams = false;
			}
			else
				return new ArrayList<Product>();
		}

		if(queryParams.containsKey("category")) {
			String categoryParam = queryParams.get("category").get(0);
			Long categoryId = NumberUtil.stringToLong(categoryParam);

			if(categoryId != null) {
				if(!noParams) {
					filter += "&& ";
					params += ", ";
				}

				filter += "this.categories.contains(categoryParam) ";
				params += "Key categoryParam";
				values.add(KeyFactory.createKey(
						Category.class.getSimpleName(), categoryId));
				noParams = false;
			}
			else
				return new ArrayList<Product>();
		}

		if(queryParams.containsKey("location")) {
	
			if(!noParams) {
				filter += "&& ";
				params += ", ";
			}

			filter += "this.locations.contains(location) "
				+ "&& location.name == locationParam ";
			params += "String locationParam";
			variables += "Location location";
			imports += "import com.bazaaroid.server.gae.core.persistence.model.partner.location;";
			values.add(queryParams.get("location").get(0));
			noParams = false;
		}

		Double priceFrom = null;
		Double priceTo = null;
		Integer discount = null;

		if(queryParams.containsKey("priceFrom")) {
			String priceFromParam = queryParams.get("priceFrom").get(0);
			priceFrom = NumberUtil.stringToDouble(priceFromParam);

			if(priceFrom == null)
				return new ArrayList<Product>();
		}

		if(queryParams.containsKey("priceTo")) {
			String priceToParam = queryParams.get("priceTo").get(0);
			priceTo = NumberUtil.stringToDouble(priceToParam);

			if(priceTo == null)
				return new ArrayList<Product>();
		}

		if(queryParams.containsKey("discount")) {
			String discountParam = queryParams.get("discount").get(0);
			discount = NumberUtil.stringToInt(discountParam);

			if(discount == null)
				return new ArrayList<Product>();
		}
		
		List<Product> results;

		if(noParams && priceFrom == null && priceTo == null && discount == null)
			results = new ArrayList<Product>();
		else if(priceFrom != null && priceTo != null && priceTo < priceFrom)
			results = new ArrayList<Product>();
		else {
			PersistenceManager persistenceManager = getPersistenceManager();
			persistenceManager.setDetachAllOnCommit(true);
			results = new ArrayList<Product>();

			Query query = persistenceManager.newQuery(Product.class);

			if(!noParams) {
				query.setFilter(filter);
				query.declareImports(imports);
				query.declareVariables(variables);
				query.declareParameters(params);
			}

			try {
				List<Product> products = (List<Product>) query.executeWithArray(
						values.toArray());
				products.size();

				if(priceFrom != null || priceTo != null || discount != null) {
					for(Product product: products) {

						Double price = null;
						Integer productDiscount = null;
						
						if(product.getDiscount() != null) {
							Discount productDiscountTemp = persistenceManager.getObjectById(
									Discount.class, product.getDiscount());

							price = productDiscountTemp.getPriceWithDiscount();
							productDiscount = productDiscountTemp.getPercentage();
						}
						else
							price = product.getPrice();

						if(priceFrom == null && priceTo == null && (discount != null && discount.equals(productDiscount))) {
							results.add(product);
						} else if(((priceFrom == null || price >= priceFrom)
								&& (priceTo == null || price <= priceTo))
								&& discount == null) {
							results.add(product);
						} else if (((priceFrom == null || price >= priceFrom)
								&& (priceTo == null || price <= priceTo))
								&& (discount != null && discount.equals(productDiscount))) {
							results.add(product);
						}
					}
				}
				else
					results = products;
			}
			finally {
				query.closeAll();
				persistenceManager.close();
			}
		}

		Collections.sort(results, priceComparator);
		return results;
	}

	public void importProductsFromFile(String filePath) {
		PersistenceManager persistenceManager =  getPersistenceManager();
		CSVReader csvReader = null;

		try {
			InputStreamReader inputStreamReader = new InputStreamReader(
					new BufferedInputStream(new FileInputStream(filePath)), "UTF-8");

			csvReader = new CSVReader(inputStreamReader, ';', '\"', 2);

			Date today = new Date();
			String [] nextLine;

			PartnerDAO partnerDAO = GuiceFactory.getInjector()
				.getInstance(PartnerDAO.class);
			LocationDAO locationDAO = GuiceFactory.getInjector()
				.getInstance(LocationDAO.class);
			CategoryDAO categoryDAO = GuiceFactory.getInjector()
				.getInstance(CategoryDAO.class);
			TagDAO tagDAO = GuiceFactory.getInjector()
				.getInstance(TagDAO.class);

			while ((nextLine = csvReader.readNext()) != null) {
				int index = 0;

				String partnerName = nextLine[index++].trim();
				String localId = nextLine[index++].trim();
				String name = nextLine[index++].trim();
				String description = nextLine[index++].trim();
				String priceStr = nextLine[index++].trim();
				String currency = nextLine[index++].trim();
				String categoriesStr = nextLine[index++].trim();
				String tagsStr = nextLine[index++].trim(); 
				String locationsStr = nextLine[index++].trim();
				String language = nextLine[index++].trim();

				Partner partner = partnerDAO.getPartnerByName(partnerName);
				Set<Key> productCategories, productTags, productLocations;

				if(partner != null) {
					Product product = getProductByPartnerAndLocalId(partner, localId);

					if(product == null) {
						product = new Product();
						product.setLocalId(localId);
						product.setCreated(today);
						productCategories = new HashSet<Key>();
						productTags = new HashSet<Key>();
						productLocations = new HashSet<Key>();
					}
					else {
						productCategories = product.getCategories();
						productTags = product.getTags();
						productLocations = product.getLocations();
					}

					if(!Languages.isSupportedLanguage(language))
						language = null;

					product.setModified(today);
					product.setLanguage(language);
					product.setName(name);
					product.setDescription(description);
					product.setPrice(NumberUtil.toDouble(priceStr, 0));
					product.setCurrency(currency);

					partner = persistenceManager.getObjectById(
							Partner.class, partner.getKey());
					partner.addProduct(product);
					persistenceManager.makePersistent(partner);

					persistenceManager.close();
					persistenceManager = getPersistenceManager();
					product = persistenceManager.getObjectById(
							Product.class, product.getKey());

					// Product categories
					String[] categories = categoriesStr.split(";");
					for(int i = 0; i < categories.length; i++) {
						Category category = categoryDAO.getCategoryByName(
								categories[i].trim());

						if(category != null) {
							if(productCategories.contains(category.getKey()))
								productCategories.remove(category.getKey());
							else {
								category = persistenceManager.getObjectById(
										Category.class, category.getKey());
								product.addCategory(category);
							}
						}
					}

					for(Key categoryKey: productCategories)
						product.removeCategory(persistenceManager.getObjectById(
								Category.class, categoryKey));

					// Product tags
					String[] tags = tagsStr.split(";");
					for(int i = 0; i < tags.length; i++) {
						String tagName = tags[i].trim();
						Tag tag = tagDAO.getTagByName(tagName);

						if(tag != null) {
							tag = persistenceManager.getObjectById(
									Tag.class, tag.getKey());
						}
						else {
							tag = new Tag();
							tag.setCreated(today);
							tag.setModified(today);
							tag.setLanguage(language);
							tag.setName(tagName);
							persistenceManager.makePersistent(tag);
							persistenceManager.close();
							persistenceManager = getPersistenceManager();
						}

						product = persistenceManager.getObjectById(
								Product.class, product.getKey());

						if(productTags.contains(tag.getKey()))
							productTags.remove(tag.getKey());
						else {
							tag = persistenceManager.getObjectById(
									Tag.class, tag.getKey());
							product.addTag(tag);
						}
					}

					for(Key tagKey: productTags) {
						product = persistenceManager.getObjectById(
								Product.class, product.getKey());

						product.removeTag(persistenceManager.getObjectById(
								Tag.class, tagKey));
					}

					// Product locations
					String[] locations = locationsStr.split(";");
					for(int i = 0; i < locations.length; i++) {
						Location location = locationDAO.getLocationByPartnerAndLocalId(
								partner, locations[i].trim());

						if(location != null) {
							if(productLocations.contains(location.getKey()))
								productLocations.remove(location.getKey());
							else {
								location = persistenceManager.getObjectById(
										Location.class, location.getKey());
								product.addLocation(location);
							}
						}
					}

					for(Key locationKey: productLocations)
						product.removeLocation(persistenceManager.getObjectById(
								Location.class, locationKey));

					persistenceManager.evictAll();
				}
			}
			csvReader.close();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			persistenceManager.close();
		}
	}
}
